Problem
Include a Javascript file in the head of of an ASP.NET WebForms Master Page whose src attribute should contain an absolute (from Application Root) path e.g.http://localhost:8080/js/jquery-1.3.2.js (single application server) or http://localhost:8080/MyProject/js/jquery-1.3.2.js (multi application server). Sounds simple enough but it's surprisingly difficult.
Attempt 1: Use a fixed path. (Partial Success)
<script src=?/js/jquery-1.3.2.js? type="text/javascript"></script>
<script src="/NIApp/js/jquery-1.3.2.js" type="text/javascript"></script>
While this "works" it isn't exactly the most resilient to server changes - what if we move to/from a single/multi application server? We need to re-write all our URL's
Attempt 2: Use Application Root Reference (~) (Fail)
<script src=?~/js/jquery-1.3.2.js? type="text/javascript"></script>
This just plain doesn't work because script tags do not get parsed by ASP page renderer so the URL doesn't get converted and what you see here is what you end up with on the page which obviously isn't correct.
Attempt 3: Use Server Code (<%= %>) (Partial Success/Fail)
<script src=?<%=Page.ResolveUrl(?~/js/jquery-1.3.2.js?)%>? type=?text/javascript?></script>
Again this is going to fail in certain situations (but not all). This will fail when your head tag has a runat="server" attribute present. In some cases you can simply remove the attribute but if you are using the "out-of-the-box" CSS Themes/Skins you NEED this.
Attempt 4: Use a Second <HEAD> Element (Partial Success)
<head>
<script src=?<%=Page.ResolveUrl(?~/js/jquery-1.3.2.js?)%>? type=?text/javascript?></script>
</head>
<head runat=?server?> ? </head>
This will work as you would expect - the only problem is that it isn't valid Markup and will fail if you throw it through W3C Validators.
Attempt 5: Add script tags "Outside" Head Element (Partial Success)
<head runat=?server?> ? </head>
<body>
<script src=?<%=Page.ResolveUrl(?~/js/jquery-1.3.2.js?)%>? type=?text/javascript?></script>
</body>
Again this will work the only problem is you can never guarantee another developer won't stick code (which is dependant on your global script) BEFORE your inclusion of said script which is a bit of a <head>ache (i.e. in the <head> element - get it?).
Attempt 6: Insert Script Tags via MasterPage Page_Load Event (Success)
protected void Page_Load(object sender, EventArgs e)
{
HtmlGenericControl script = new HtmlGenericControl('script');
script.Attributes.Add('type', 'text/javascript');
script.Attributes.Add('src', ResolveUrl('~/js/jquery-1.3.2.js'));
Page.Header.Controls.AddAt(0, script);
}
I suppose this is the first real successful attempt. It does what it says by dynamically creating new Script tag element and inserting them to the top of <head>. I inserted everything at the top of the head so no matter where people put their scripts it's going to have all global resources available. The only problem with this is that EVERY Page_Load event is going to dynamically inject new Elements into the head and it's not exactly a very clean separation of mark-up and code.
Attempt 7/Solution 1: Use Data Binding to Evaluate src Attribute (AKA The Holmes Technique)
ASPX
<head runat="server">
<script type="text/javascript" src=?<%# ResolveUrl("~/js/jquery-1.3.2.js")%>?> </script>
<asp:ContentPlaceHolder ID="head" runat="server"></asp:ContentPlaceHolder>
</head>
Jonny Holmes uncovered a more elegant solution to this issue and it is described above. It gets around the issue of not being able to use <%= %> tags by using a Data Binding Expression. And all you have to do is perform a DataBind() during the Page_Load event and you're sorted like Ebenezer.
protected void Page_Load(object sender, EventArgs e)
{
//Called for the javascript references in the header of the master page
Page.Header.DataBind();
}
Anyone got any other methods to do this?